﻿/* ************************************
*《精通Windows API》
* 示例代码
* client.c
* 14.1 socket通信
*window应用程序网络连接框架
**************************************/
/* 头文件 */
#include <stdio.h>
#include <windows.h>
#include "winsock2.h"
/* 常量 */
#define RECV_BUFFER_SIZE 8192 //宏定义接收字符串的大小

/*************************************
* main
* 功能 socket通信客户端
**************************************/
void main(int argc, char* argv[])
{
	// 变量定义
	SOCKADDR_IN clientService;// 地址
	/*
		SOCKADDR_IN是结构体sockaddr_in的别名，同时这个结构体在定义时加了typedef,
		所以SOCKADDR_IN clientService中的clientService也相当于是sockaddr_in结构体的一个别名

	*/
	SOCKET ConnectSocket;// socket
	/*
	typedef UINT_PTR SOCKET 关于UINT_PTR的解释是这样的：
	The new type to be used in all instances which refer to sockets.
	ConnectSocket等价于UINT_PTR的定义
	*/
	WSADATA wsaData;// 库
	/*
	WSAData是结构体WSAData的一个别名，同clientService变量的定义
	*/
	LPVOID recvbuf;// 接收缓存
	/*
	typedef void far  *LPVOID;LPVOID recvbuf声明等价于void far *recvbuf 
	*/
	int bytesSent;
	int bytesRecv = 0;
	char sendbuf[32] = "get information";// 默认发送的数据

	// 初始化socket库，	保存ws2_32.dll已经加载
	int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	/*WSAStartup函数
	WSAStartup 函数的功能是加载 Ws2_32.dll 等 Socket 程序运行的环境。
	在程序初始化 后，Socket  程序运行所依赖的动态链接库不一定已经加载，
	WSAStartup 保证了 Socket 动 态链接库的加载。
	wVersionRequested 参数：是 Socket 程序库的版本，
	一般使用 MAKEWORD(2,2)宏。 lpWSAData 参数：输出参数，指向 WSADATA 结构的指针，用于返回 Socket 库初始化的信息。
	WSAStartup 的返回值可以判断程序是否调用成功。
	*/
	if (iResult != NO_ERROR)
		printf("Error at WSAStartup()\n");

	// 创建socket
	ConnectSocket = socket(AF_INET, // IPv4
		SOCK_STREAM, // 顺序的、可靠的、基于连接的、双向的数据流通信
		IPPROTO_TCP// 使用TCP协议
		);
	/*
	Socket 函数的功能是建立一个绑定到指定协议和传输类型的 Socket。
	af (af 是 address family 的缩写)参数：指定了这个 Socket 使用哪一种类型的网络地址，
	可以是 IPv4 (AF INET)，也可以是 IPv6 (AF INET6)、蓝牙(AF BTM)、NetBios (AF NETBIOS) 等。
	type 参数：指的传输类型，可设置为 SOCK_STREAM、SOCK DGRAM、SOCK- RAW、
	SOCK_SEQPACKET 等。SOCK．-STREAM 类型是基于连接的(TCP)，所收的数据是数据流形式的，
	传输层的数据包已经经过处理，  SOCK DGRAM 是基于报文的(UDP)。如果指定为 SOCK_ RAW，
	那么可以建立原始套接字，所收到的数据是以数据包（包括包头）的形式存在的。
	protocol 参数：指明了使用的传输协议，常用的是 IPPROTO_TCP 和 IPPROTO_UDP。
	*/
	if (ConnectSocket == INVALID_SOCKET)
	{
		printf("Error at socket(): %ld\n", WSAGetLastError());
		/*返回上次发生的网络错误*/
		WSACleanup();
		/*
		WSACleanup 释放 Ws2_32.dll 库，函数无参数。
		*/
		return;
	}

	// 设置服务端的通信协议、IP地址、端口
	clientService.sin_family = AF_INET;
	clientService.sin_addr.s_addr = inet_addr( "127.0.0.1" );//本地
	clientService.sin_port = htons( 10000 );

	// 连接到服务端
	if ( connect(
		ConnectSocket, // socket
		(SOCKADDR*) &clientService, // 地址
		sizeof(clientService) // 地址的大小
		) == SOCKET_ERROR)
	{
		printf( "Failed to connect(%d)\n",WSAGetLastError() );
		WSACleanup();
		return;
	}
	/*
	connect 函数的功能是与服务端建立连接。这个函数只能由服务端程序调用。
	s 参数：由 socket 函数建立的套接字。
	name 参数：指向 sockaddr 结构的指针，包括了所要连接的服务端的地址和端口等。
	namelen: name 参数指向的 sockaddr 结构的长度。
	*/
	// 准备发送数据
	// 如果输入参数是-d，那么发送的数据是“download file”否则是"get information"
	if(argc ==2 && (!lstrcmp(argv[1], "-d")))
	{
		lstrcpyn(sendbuf, "download file", 32);
	}
	// 向服务端发送数据
	bytesSent = send( ConnectSocket, // socket
		sendbuf,// 发送的数据 
		lstrlen(sendbuf)+1,// 数据长度
		0 );// 无标志
	/*send 函数的功能是向连接的另一端发送数据，函数原型如下： 
    参数 s 是由 socket 函数返回的套接字。buf 指向需要发送的数据，
	len 是发送数据的长 度。flags 参数表示的发送的方法，其值可以是
	MSG_- DONTROUTE、MSG_OOB 等，一般可设 置为 0。    
	如果 send 成功，则返回实际发送的数据；
	如果失败，返回 SOCKET ERROR。*/

	if(bytesSent == SOCKET_ERROR)
	{
		printf( "send error (%d)\n", WSAGetLastError());
		closesocket(ConnectSocket);
		return;
	}
	/*colsesocket 函数用于关闭 Socket*/
	printf( "Bytes Sent: %ld\n", bytesSent );

	// 准备接收数据
	recvbuf = HeapAlloc(GetProcessHeap(), 0, RECV_BUFFER_SIZE);
	/*
	用来在指定的堆上分配内存，并且分配后的内存不可移动
	参数：1.要分配堆的句柄，可以通过HeapCreate()函数或GetProcessHeap()函数获得。
	     2.堆分配时的可选参数 
			a.HEAP_GENERATE_EXCEPTIONS如果分配错误将会抛出异常，而不是返回NULL。
		 异常值可能是STATUS_NO_MEMORY, 表示获得的内存容量不足，或是STATUS_ACCESS_VIOLATION,表示存取不合法。
			b.HEAP_NO_SERIALIZE 不使用连续存取。
			c.HEAP_ZERO_MEMORY 将分配的内存全部清零。
	*/
	// 循环接收
	while( bytesRecv != SOCKET_ERROR )
	{
		//Sleep(50);
		bytesRecv = recv( ConnectSocket, // socket
			recvbuf, // 接收数据缓存
			RECV_BUFFER_SIZE,// 缓存大小
			0 );// 无标志
		/*
		recv 函数的功能是从连接的另外一端接收数据，函数原型如下：
		参数 s 是由 socket 函数返回的套接字，buf 指向用于保存接收数据的缓存，
		len 是接收 缓存的大小长度。flags 参数表示接收的方法，其值可以是 MSG DONIROUTE、MSG_OOB 等，
		一般可设置为 0。     
		系统在实现 TCP 协议时都为数据的接收保留了缓存。协议收到数据包后，解包并将数据 放入缓存中，
		直到 recv 函数将数据接收。如果 recv 函数长时间不接收数据，在协议的缓存
		存满之后，对方的 send 函数就不能再发送数据（更详细的原理可参考 TCP 协议的滑动窗口 机制）。
		*/
		if ( bytesRecv == 0 )
		{
			printf( "Connection Closed.\n");
			break;
		}
		// TODO，处理接收的数据，这里只简单的将收到的数据大小显示
		printf( "Bytes Recv: %ld\n", bytesRecv );
	}
	HeapFree(GetProcessHeap(), 0, recvbuf);
	/*
	它用来释放堆内存
	*/
	WSACleanup();
	return;
}
